package han.chensing.latos.maker.windows.dialog.level;

import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import han.chensing.latos.common.level.content.timeLine.events.TimeLineEvent;
import han.chensing.latos.common.level.content.timeLine.events.TimeLineMotionEvent;
import han.chensing.latos.common.level.content.timeLine.frame.BaseFrame;
import han.chensing.latos.common.level.content.timeLine.frame.FrameGeneral;
import han.chensing.latos.common.level.content.timeLine.frame.GeneralEventTypes;
import han.chensing.latos.common.level.content.timeLine.motion.ValueMotion;
import han.chensing.latos.common.level.content.timeLine.motion.interpolator.Bezier2Interpolator;
import han.chensing.latos.common.level.content.timeLine.motion.interpolator.Interpolator;
import han.chensing.latos.common.level.util.FrameUtil;
import han.chensing.latos.common.level.util.MathUtil;
import han.chensing.latos.common.level.util.bind_event.BindEventType;
import han.chensing.latos.common.level.util.bind_event.BindEventTypeExtra;
import han.chensing.latos.maker.V;
import han.chensing.latos.maker.latos.file.util.ComParser;
import han.chensing.latos.maker.res.Res;
import han.chensing.latos.maker.windows.dialog.base.BaseDialog;
import han.chensing.latos.maker.windows.util.BatchEventTypesWidgetsAdd;
import han.chensing.latos.maker.windows.widget.BezierEditorWidget;
import han.chensing.latos.maker.windows.widget.ShortCutWidget;
import han.chensing.latos.maker.windows.widget.simulator.LatosSimulatorWidget;
import han.chensing.latos.maker.windows.widget.timeline.TimeLineWidget;
import han.chensing.latos.maker.windows.widget.timeline.TimeLineWidgetUtil;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Locale;

public class LevelEffectEditorDialog extends BaseDialog
        implements
        TimeLineWidget.TimeLineWidgetUpdateInterface,
        BezierEditorWidget.BezierEditorWidgetUpdateInterface {

    TimeLineWidget timeLineWidget;
    LatosSimulatorWidget latosSimulatorWidget;
    BezierEditorWidget bezierEditorWidget;
    ShortCutWidget shortCutWidget;
    TextField nowTimeField;
    CheckBox showEventNameCheck;
    CheckBox beatModeCheck;
    TextButton adsorbToBeatCheck;
    TextButton adsorbToNoteCheck;
    TextButton adsorbToKeyFrameCheck;
    TextButton adsorbDisableCheck;
    ButtonGroup<TextButton> adsorbButtonGroup;
    TextButton applyKeyFrameButton;
    TextButton applyEventButton;
    TextButton musicPlayButton;
    TextButton musicPauseButton;
    @BindEventType(GeneralEventTypes.X) Label lineXLabel;
    @BindEventType(GeneralEventTypes.Y) Label lineYLabel;
    @BindEventType(GeneralEventTypes.Rotate) Label lineRotateLabel;
    @BindEventType(GeneralEventTypes.Alpha) Label lineAlphaLabel;
    @BindEventType(GeneralEventTypes.Speed) Label lineSpeedLabel;
    @BindEventType(GeneralEventTypes.X) @BindEventTypeExtra(TYPE_EXTRA_NORMAL) TextField lineXField;
    @BindEventType(GeneralEventTypes.X) @BindEventTypeExtra(TYPE_EXTRA_FROM) TextField lineXFromField;
    @BindEventType(GeneralEventTypes.X) @BindEventTypeExtra(TYPE_EXTRA_TO) TextField lineXToField;
    @BindEventType(GeneralEventTypes.Y) @BindEventTypeExtra(TYPE_EXTRA_NORMAL) TextField lineYField;
    @BindEventType(GeneralEventTypes.Y) @BindEventTypeExtra(TYPE_EXTRA_FROM) TextField lineYFromField;
    @BindEventType(GeneralEventTypes.Y) @BindEventTypeExtra(TYPE_EXTRA_TO) TextField lineYToField;
    @BindEventType(GeneralEventTypes.Rotate) @BindEventTypeExtra(TYPE_EXTRA_NORMAL) TextField lineRotateField;
    @BindEventType(GeneralEventTypes.Rotate) @BindEventTypeExtra(TYPE_EXTRA_FROM) TextField lineRotateFromField;
    @BindEventType(GeneralEventTypes.Rotate) @BindEventTypeExtra(TYPE_EXTRA_TO) TextField lineRotateToField;
    @BindEventType(GeneralEventTypes.Alpha) @BindEventTypeExtra(TYPE_EXTRA_NORMAL) TextField lineAlphaField;
    @BindEventType(GeneralEventTypes.Alpha) @BindEventTypeExtra(TYPE_EXTRA_FROM) TextField lineAlphaFromField;
    @BindEventType(GeneralEventTypes.Alpha) @BindEventTypeExtra(TYPE_EXTRA_TO) TextField lineAlphaToField;
    @BindEventType(GeneralEventTypes.Speed) @BindEventTypeExtra(TYPE_EXTRA_NORMAL) TextField lineSpeedField;
    @BindEventType(GeneralEventTypes.Speed) @BindEventTypeExtra(TYPE_EXTRA_FROM) TextField lineSpeedFromField;
    @BindEventType(GeneralEventTypes.Speed) @BindEventTypeExtra(TYPE_EXTRA_TO) TextField lineSpeedToField;
    TextField bezierX1Field;
    TextField bezierY1Field;
    TextField bezierX2Field;
    TextField bezierY2Field;
    TextButton testButton;

    private static final int TYPE_EXTRA_NORMAL=0;
    private static final int TYPE_EXTRA_FROM=1;
    private static final int TYPE_EXTRA_TO=2;

    public LevelEffectEditorDialog() {
        super("Level Editing");

        Skin skin = Res.get().get("skinBasic", Skin.class);
        setSize(2300,1350);

        nowTimeField=new TextField("NaN",skin);
        nowTimeField.setBounds(20,20,250,30);
        addActor(nowTimeField);

        showEventNameCheck=new CheckBox("Show event name",skin);
        showEventNameCheck.setBounds(250,20,250,30);
        addActor(showEventNameCheck);

        beatModeCheck=new CheckBox("Beat timeline",skin);
        beatModeCheck.setBounds(510,20,100,30);
        addActor(beatModeCheck);

        adsorbToBeatCheck=new TextButton("Adsorb to beat",skin,"toggle");
        adsorbToBeatCheck.setBounds(650,20,140,30);
        addActor(adsorbToBeatCheck);

        adsorbToNoteCheck=new TextButton("Adsorb to Notes",skin,"toggle");
        adsorbToNoteCheck.setBounds(790,20,140,30);
        addActor(adsorbToNoteCheck);

        adsorbToKeyFrameCheck=new TextButton("Adsorb to Frames",skin,"toggle");
        adsorbToKeyFrameCheck.setBounds(930,20,140,30);
        addActor(adsorbToKeyFrameCheck);

        adsorbDisableCheck=new TextButton("Disable adsorbing",skin,"toggle");
        adsorbDisableCheck.setBounds(1070,20,140,30);
        adsorbDisableCheck.setChecked(true);
        addActor(adsorbDisableCheck);

        adsorbButtonGroup=new ButtonGroup<>(adsorbDisableCheck,adsorbToKeyFrameCheck,adsorbToBeatCheck,adsorbToNoteCheck);
        adsorbDisableCheck.setChecked(true);

        applyKeyFrameButton =new TextButton("Apply Frame",skin);
        applyKeyFrameButton.setBounds(1760,1078,200,30);
        addActor(applyKeyFrameButton);

        applyEventButton=new TextButton("Apply Event",skin);
        applyEventButton.setBounds(1980,478,200,30);
        addActor(applyEventButton);

        BatchEventTypesWidgetsAdd.of(
                this,
                i->1680f,i->1288f - 42f * i,
                i->70f,i->30f,
                Label.class,
                BindEventTypeExtra.DEFAULT_EXTRA,
                skin,
                (index, fieldArg, theSkin) -> new Label(fieldArg, theSkin)
        ).start();
        BatchEventTypesWidgetsAdd.of(
                this,
                i->1680f+15+70f,i->1288f - 42f * i,
                i->200f,i->30f,
                TextField.class,
                TYPE_EXTRA_NORMAL,
                skin,
                (index, fieldArg, theSkin) -> new TextField("", theSkin)
        ).start();
        BatchEventTypesWidgetsAdd.of(
                this,
                i->1680f+15+70f,i->1288f - 42f * i,
                i->200f/2f,i->30f,
                TextField.class,
                TYPE_EXTRA_FROM,
                skin,
                (index, fieldArg, theSkin) -> new TextField("", theSkin)
        ).start();
        BatchEventTypesWidgetsAdd.of(
                this,
                i->1680f+15+70f+200/2f,i->1288f - 42f * i,
                i->200f/2f,i->30f,
                TextField.class,
                TYPE_EXTRA_TO,
                skin,
                (index, fieldArg, theSkin) -> new TextField("", theSkin)
        ).start();
        /*for(int i=0;i<EventType.values().length;i++)
            DialogUtil.addEventTypeLabelFieldSetWithFromAndEnd(
                    this, EventType.values()[i].name(),1680,1288-42*i,"line");*/

        bezierX1Field=new TextField("",skin);
        bezierX1Field.setBounds(2190,1288,70,30);
        addActor(bezierX1Field);
        bezierY1Field=new TextField("",skin);
        bezierY1Field.setBounds(2190,1246,70,30);
        addActor(bezierY1Field);
        bezierX2Field=new TextField("",skin);
        bezierX2Field.setBounds(2190,1204,70,30);
        addActor(bezierX2Field);
        bezierY2Field=new TextField("",skin);
        bezierY2Field.setBounds(2190,1162,70,30);
        addActor(bezierY2Field);

        shortCutWidget=new ShortCutWidget();
        shortCutWidget.setBounds(2190,10,100,30);
        shortCutWidget.register(114514,"I",true, Input.Keys.I);
        addActor(shortCutWidget);

        timeLineWidget=new TimeLineWidget(
                nowTimeField,
                showEventNameCheck,
                beatModeCheck,
                adsorbButtonGroup,
                shortCutWidget,
                this);
        timeLineWidget.setBounds(20,60,2200,300);
        addActor(timeLineWidget);

        latosSimulatorWidget =new LatosSimulatorWidget();
        latosSimulatorWidget.setBounds(20,390,1650,928);
        latosSimulatorWidget.init();
        addActor(latosSimulatorWidget);

        bezierEditorWidget=new BezierEditorWidget(this);
        bezierEditorWidget.setBezier(new Bezier2Interpolator(.5f,.5f,.5f,.5f));
        bezierEditorWidget.setBounds(1980,918,200,200);
        addActor(bezierEditorWidget);

        testButton=new TextButton("Test",skin);
        testButton.setBounds(1970,795,100,30);
        addActor(testButton);

        musicPlayButton=new TextButton("Play",skin);
        musicPlayButton.setBounds(1260,20,100,30);
        addActor(musicPlayButton);

        musicPauseButton=new TextButton("Pause",skin);
        musicPauseButton.setBounds(1370,20,100,30);
        addActor(musicPauseButton);

        moveToCenter();

        class BezierChangeListener extends ChangeListener{
            final String fieldName;
            public BezierChangeListener(String fieldName) {
                this.fieldName = fieldName;
            }
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                if (!(actor instanceof TextField textField))return;
                try {
                    String text = textField.getText();
                    changeBezierConfig(fieldName, Float.parseFloat(text));
                }catch (Exception ignore){}
            }
        }
        bezierX1Field.addListener(new BezierChangeListener("X1"));
        bezierY1Field.addListener(new BezierChangeListener("Y1"));
        bezierX2Field.addListener(new BezierChangeListener("X2"));
        bezierY2Field.addListener(new BezierChangeListener("Y2"));
        applyKeyFrameButton.addListener(new ClickListener(){
            @Override
            public void clicked(InputEvent event, float x, float y) {
                super.clicked(event, x, y);
                applyKeyFrame();
            }
        });
        applyEventButton.addListener(new ClickListener(){
            @Override
            public void clicked(InputEvent event, float x, float y) {
                super.clicked(event, x, y);
                applyEvent();
            }
        });
        testButton.addListener(new ClickListener(){
            @Override
            public void clicked(InputEvent event, float x, float y) {
                super.clicked(event, x, y);
                try {
                    ComParser.writeToZipFile(V.project);
                }catch (Exception ignore){
                }
            }
        });
        musicPlayButton.addListener(new ClickListener(){
            @Override
            public void clicked(InputEvent event, float x, float y) {
                super.clicked(event, x, y);
                V.project.getMusic().play();
            }
        });
        musicPauseButton.addListener(new ClickListener(){
            @Override
            public void clicked(InputEvent event, float x, float y) {
                super.clicked(event, x, y);
                V.project.getMusic().pause();
            }
        });

        timeLineWidget.init();
    }

    private void setVisibilitiesOfEventEditingArea(boolean visible){
        bezierX1Field.setVisible(visible);
        bezierY1Field.setVisible(visible);
        bezierX2Field.setVisible(visible);
        bezierY2Field.setVisible(visible);
        bezierEditorWidget.setVisible(visible);
        applyEventButton.setVisible(visible);
    }

    private void setEventSplitField(TimeLineMotionEvent timeLineMotionEvent){
        try {
            for (GeneralEventTypes value : GeneralEventTypes.values()) {
                setSingleSplitField(timeLineMotionEvent, value.name());
            }
        }catch (Exception ignore){}
    }

    private void setSingleSplitField(TimeLineMotionEvent timeLineMotionEvent,String name)throws Exception{
        Class<LevelEffectEditorDialog> levelEffectEditorDialogClass = LevelEffectEditorDialog.class;
        String lineFromFieldName=String.format(Locale.getDefault(),"line%sFromField",name);
        String lineToFieldName=String.format(Locale.getDefault(),"line%sToField",name);
        Field lineFromField = levelEffectEditorDialogClass.getDeclaredField(lineFromFieldName);
        Field lineToField=levelEffectEditorDialogClass.getDeclaredField(lineToFieldName);
        TextField lineFromTextField = (TextField) lineFromField.get(this);
        TextField lineToTextField = (TextField) lineToField.get(this);
        boolean currentType = timeLineMotionEvent!=null&&timeLineMotionEvent.getValueMotion().getType().name().equals(name);
        lineFromTextField.setVisible(currentType);
        lineToTextField.setVisible(currentType);
        if (!currentType)return;
        lineFromTextField.setText(timeLineMotionEvent.getValueMotion().getStartValue()+"");
        lineToTextField.setText(timeLineMotionEvent.getValueMotion().getEndValue()+"");
    }

    private void changeBezierConfig(String fieldName, float value){
        if (!MathUtil.isInRange(0,value,1))return;
        try{
            Class<BezierEditorWidget> bezier2InterpolatorClass = BezierEditorWidget.class;
            Method method = bezier2InterpolatorClass.getMethod(String.format("set%s", fieldName),float.class);
            method.invoke(bezierEditorWidget,value);
            bezierEditorWidget.regenMap();
        }catch (Exception ignore){}
    }

    private Bezier2Interpolator getNowSelectedBezierInterpolator(){
        TimeLineEvent<?> nowLineEvent = getNowLineEvent();
        if (nowLineEvent == null) return null;
        if (!(nowLineEvent instanceof TimeLineMotionEvent timeLineMotionEvent))return null;
        ValueMotion valueMotion = timeLineMotionEvent.getValueMotion();
        if (valueMotion ==null)return null;
        Interpolator interpolator = valueMotion.getInterpolator();
        if (interpolator==null)return null;
        if (!(interpolator instanceof Bezier2Interpolator bezier2Interpolator))return null;
        return bezier2Interpolator;
    }

    private void setATimeLineMotionEventByType(TimeLineMotionEvent timeLineMotionEvent, GeneralEventTypes generalEventTypes) throws Exception{
        String name= generalEventTypes.name();
        Class<LevelEffectEditorDialog> levelEffectEditorDialogClass = LevelEffectEditorDialog.class;
        String lineFromFieldName=String.format(Locale.getDefault(),"line%sFromField",name);
        String lineToFieldName=String.format(Locale.getDefault(),"line%sToField",name);
        Field lineFromField = levelEffectEditorDialogClass.getDeclaredField(lineFromFieldName);
        Field lineToField=levelEffectEditorDialogClass.getDeclaredField(lineToFieldName);
        TextField lineFromTextField = (TextField) lineFromField.get(this);
        TextField lineToTextField = (TextField) lineToField.get(this);
        timeLineMotionEvent.getValueMotion().setStartValue(Float.parseFloat(lineFromTextField.getText()));
        timeLineMotionEvent.getValueMotion().setEndValue(Float.parseFloat(lineToTextField.getText()));
    }

    private TimeLineEvent<?> getNowLineEvent() {
        TimeLineWidgetUtil.Selection nowSelected = timeLineWidget.getNowSelected();
        if (nowSelected==null) return null;
        if (nowSelected.timeLineEventSetIndex==-1) return null;
        return FrameUtil.getNowLineEvent(timeLineWidget.getNowTime(), nowSelected.timeLineEventSet);
    }

    private void applyEvent(){
        applyEventBezier();
        applyEventValue();
    }

    private void applyEventValue(){
        TimeLineEvent<?> nowLineEvent = getNowLineEvent();
        if (nowLineEvent==null)return;
        if (!(nowLineEvent instanceof TimeLineMotionEvent timeLineMotionEvent))return;
        if (!(timeLineWidget.getNowSelected().timeLineEventSet.getType() instanceof GeneralEventTypes type)) return;
        try {
            setATimeLineMotionEventByType(timeLineMotionEvent,type);
        } catch (Exception ignore) {}
    }

    private void applyEventBezier(){
        Bezier2Interpolator bezier2Interpolator = getNowSelectedBezierInterpolator();
        if (bezier2Interpolator==null)return;
        float[] bezier = bezierEditorWidget.getBezier();
        bezier2Interpolator.setPosition(bezier[0],bezier[1],bezier[2],bezier[3]);
    }

    private void applyKeyFrame(){//FIXME add sth for FrameEditor
        BaseFrame<?> nowKeyFrame = timeLineWidget.getNowKeyFrame();
        if (nowKeyFrame instanceof FrameGeneral frameGeneral) {
            for (GeneralEventTypes generalEventTypes : frameGeneral.getEventTypes()) {
                if (generalEventTypes == null) continue;
                switch (generalEventTypes) {
                    case X -> frameGeneral.setX(Float.parseFloat(lineXField.getText()));
                    case Y -> frameGeneral.setY(Float.parseFloat(lineYField.getText()));
                    case Alpha -> frameGeneral.setAlpha(Float.parseFloat(lineAlphaField.getText()));
                    case Rotate -> frameGeneral.setRotate(Float.parseFloat(lineRotateField.getText()));
                    case Speed -> frameGeneral.setSpeed(Float.parseFloat(lineSpeedField.getText()));
                }
            }
        }
    }

    private void setBezierFields(float x1,float y1,float x2,float y2){
        bezierX1Field.setText(String.valueOf(x1));
        bezierX2Field.setText(String.valueOf(x2));
        bezierY1Field.setText(String.valueOf(y1));
        bezierY2Field.setText(String.valueOf(y2));
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        super.draw(batch, parentAlpha);
        latosSimulatorWidget.setNowTime(timeLineWidget.getNowTime());
    }

    @Override
    public void onFrameInfoUpdated(BaseFrame<?> baseFrame) {
        if (baseFrame instanceof FrameGeneral frame) {
            lineXField.setText(String.valueOf(frame.getX()));
            lineYField.setText(String.valueOf(frame.getY()));
            lineRotateField.setText(String.valueOf(frame.getRotate()));
            lineAlphaField.setText(String.valueOf(frame.getAlpha()));
            lineSpeedField.setText(String.valueOf(frame.getSpeed()));
        }
    }

    @Override
    public void onKeyFrameUpdated(BaseFrame<?> baseFrame) {
        if (baseFrame instanceof FrameGeneral frame) {
            boolean isX = frame.hasType(GeneralEventTypes.X);
            boolean isY = frame.hasType(GeneralEventTypes.Y);
            boolean isRotate = frame.hasType(GeneralEventTypes.Rotate);
            boolean isAlpha = frame.hasType(GeneralEventTypes.Alpha);
            boolean isSpeed = frame.hasType(GeneralEventTypes.Speed);
            lineXField.setDisabled(!isX);
            lineYField.setDisabled(!isY);
            lineRotateField.setDisabled(!isRotate);
            lineAlphaField.setDisabled(!isAlpha);
            lineSpeedField.setDisabled(!isSpeed);
        }
    }

    @Override
    public void onMotionEventUpdated(TimeLineMotionEvent timeLineMotionEvent) {
        setEventSplitField(timeLineMotionEvent);
        if (timeLineMotionEvent==null){
            setVisibilitiesOfEventEditingArea(false);
            return;
        }
        Interpolator interpolator = timeLineMotionEvent.getValueMotion().getInterpolator();
        if (!(interpolator instanceof Bezier2Interpolator bezier2Interpolator))return;
        bezierEditorWidget.setBezier(bezier2Interpolator);
        float duration = timeLineMotionEvent.getToTime() - timeLineMotionEvent.getFromTime();
        float pastedTime=timeLineWidget.getNowTime()-timeLineMotionEvent.getFromTime();
        bezierEditorWidget.setTimePercent(pastedTime/duration);
        setBezierFields(
                bezier2Interpolator.getX1(),
                bezier2Interpolator.getY1(),
                bezier2Interpolator.getX2(),
                bezier2Interpolator.getY2());
        setVisibilitiesOfEventEditingArea(true);
    }

    @Override
    public void onBezierUpdated(float x1, float y1, float x2, float y2) {
        setBezierFields(x1, y1, x2, y2);
    }
}
